﻿module GameLogic

    open System
    open Microsoft.FSharp.Core.LanguagePrimitives
    open Microsoft.Xna.Framework
    open Microsoft.Xna.Framework.Input
    open Utilities
    open Casanova
    open Coroutines
        
    open InputManagement
    open GameState

    let Shoot(b : bool) = 
        isShooting <- b
    
    let SuperShot(b : bool) =
        isSuperShot <- b

 
    let mutable game_state = 
        setState "Tutorial"

    let resetState (level : string) = 
        game_state <- setState level

    let rec updateState (dt:float32) =
        
        updatePlayer dt

        if game_state.Timer = 0 then
            game_state.Level <- "Square"
        if game_state.Timer = 25 then
            game_state.Level <- "Triangle"
        elif game_state.Timer = 50 then
            game_state.Level <- "Line"
        elif game_state.Timer = 75 then
            game_state.Level <- "Point"

        for e in !game_state.Enemies do
            updateEnemy e dt


        game_state.Enemies := 
            [for e in game_state.Enemies.Value do
                if (!e.Position).X > -1.0f && (!e.Position).X <= screenWidth + 60.0f && (!e.Position).Y > 0.0f && (!e.Position).Y <= screenHeight + 60.0f && !e.IsActive = true then
                    yield e]


        game_state.Player.Projectiles := 
            [for p in game_state.Player.Projectiles.Value do
                if (!p.Position).X > 0.0f && (!p.Position).X < screenWidth + 1.0f && (!p.Position).Y > 0.0f && (!p.Position).Y < screenHeight + 1.0f && !p.isActive = true then
                    yield p
                ]

    and private updatePlayer (dt:float32) =

        let min = Vector3(game_state.Player.Position.Value.X, game_state.Player.Position.Value.Y, 0.0f)
        let max = Vector3(game_state.Player.Position.Value.X + 40.0f, game_state.Player.Position.Value.Y + 40.0f, 0.0f)
        game_state.Player.BBox <- BoundingBox(min,max)


        game_state.Player.Position := !game_state.Player.Position + !game_state.Player.Velocity * dt

        if game_state.Player.Position.Value.Y <= 150.0f then
           game_state.Player.Position := !game_state.Player.Position + Vector2(0.0f, 0.05f) 
        if game_state.Player.Position.Value.Y > screenHeight - 40.0f then
           game_state.Player.Position := !game_state.Player.Position - Vector2(0.0f, 0.05f) 
                   
            
        for p in !game_state.Player.Projectiles do
            updatePlayerProjectiles p dt 
                    
        for e in !game_state.Enemies do
            if e.BBox.Intersects(game_state.Player.BBox) then
                game_state.Player.Alive <- false
                game_state.GameOver <- true
                game_state.BestTime <- game_state.Timer
                game_state.Player.Event <- Some({i = 1; s = "Dead"})
                
            else
                game_state.GameOver <- false
        

    and private updatePlayerProjectiles (p:Projectile) (dt:float32) =
        p.Position := !p.Position + dt * p.Velocity
        p.BBox.Min.X <- p.BBox.Min.X + dt * p.Velocity.X 
        p.BBox.Max.X <- p.BBox.Max.X + dt * p.Velocity.X
        p.BBox.Min.Y <- p.BBox.Min.Y + dt * p.Velocity.Y
        p.BBox.Max.Y <- p.BBox.Max.Y + dt * p.Velocity.Y 
        p.isActive := true

        for e in !game_state.Enemies do
            if e.BBox.Intersects(p.BBox) then
                if p.Type = 1 then
                    p.isActive := false
                

    and private updateEnemy (e:Enemy) (dt:float32) =
        
        // sort steering behaviour (Seek) TODO: implement other behaviour 
        let desiredVelocity = Vector2.Normalize(!game_state.Player.Position - !e.Position) * e.Speed
        let steeringForce = desiredVelocity - !e.Velocity
        let acceleration = steeringForce / e.Mass

        e.Velocity := !e.Velocity + acceleration * dt
        e.Position := !e.Position + !e.Velocity * dt

        let angle = Math.Atan2(desiredVelocity.X |> float, desiredVelocity.Y |> float) |> float32

        if e.Type = 0 then
            e.Heading <- -angle + MathHelper.PiOver2
        else if e.Type = 1 then
            e.Heading <- -angle + MathHelper.Pi
        else if e.Type = 2 then
            e.Heading <- -angle
        else if e.Type = 3 then
            e.Heading <- angle


        e.BBox.Min.X <- e.BBox.Min.X + dt * e.Velocity.Value.X
        e.BBox.Max.X <- e.BBox.Max.X + dt * e.Velocity.Value.X
        e.BBox.Min.Y <- e.BBox.Min.Y + dt * e.Velocity.Value.Y
        e.BBox.Max.Y <- e.BBox.Max.Y + dt * e.Velocity.Value.Y        
         
        e.IsActive := true

        for p in !game_state.Player.Projectiles do
            if p.BBox.Intersects(e.BBox) then
                e.IsActive := false
    
    let private (!) = immediate_read
    let private (:=) = immediate_write
    
    let private main =

        let rec Timer() =
            co{
                do! wait 1.0 
                match game_state.Timer with
                | 300 -> 
                        game_state.Timer <- 0
                        return ()
                | t ->
                        game_state.Timer <- game_state.Timer + 1
                        do! Timer() 
            }

        let playerMove() =
            co{
                do! yield_
                let inputState = InputManager.GetState()
                let startPos = !game_state.Player.Position
                let mouseX = inputState.mouseX |> float32
                let mouseY = inputState.mouseY |> float32
                let mousePos = Vector2(mouseX, mouseY)
                let direction = mousePos - startPos
                let normDir = Vector2.Normalize(direction)
                let rotation = Math.Atan2(normDir.X |> float, normDir.Y |> float) |> float32
                let angle = -rotation + MathHelper.PiOver2
                game_state.Player.DirectionFire := 
                    angle
                                    
                game_state.Player.Velocity := Vector2(inputState.directionMove.X, inputState.directionMove.Y) * game_state.Player.Speed

                return ()
               
              }
              
        let shootProjectiles() =
            co{
                do! yield_

                if isSuperShot then
                    do! wait 1.0
                    game_state.Player.Projectiles :=
                        {
                            
                            Position        = 
                                variable(Vector2 (game_state.Player.Position.Value.X, game_state.Player.Position.Value.Y))
                            Velocity        =                                 
                                let vx = cos(!game_state.Player.DirectionFire)
                                let vy = sin(!game_state.Player.DirectionFire)
                                Vector2(vx,vy) * 1000.0f
                            CollidersA      = Variable(fun () -> [])
                            Rotation        = game_state.Player.DirectionFire.Value
                            isActive        = variable(true)
                            Event           = None
                            BBox            = 
                                new BoundingBox(new Vector3(game_state.Player.Position.Value.X, game_state.Player.Position.Value.Y, 0.0f), new Vector3(game_state.Player.Position.Value.X + 100.0f, game_state.Player.Position.Value.Y + 100.0f, 0.0f)) 
                            Type            = 2
                        }:: !game_state.Player.Projectiles

                    game_state.Player.Event <- Some({i = 0; s = "Shot"})
                    do! yield_
                    game_state.Player.Event <- None

                    return ()

                if isShooting then
                    do! wait 0.02
                    game_state.Player.Projectiles :=
                        {
                            Position        = 
                                variable(Vector2 (game_state.Player.Position.Value.X, game_state.Player.Position.Value.Y))
                            Velocity        =                                 
                                let vx = cos(!game_state.Player.DirectionFire)
                                let vy = sin(!game_state.Player.DirectionFire)
                                Vector2(vx,vy) * 800.0f
                            CollidersA      = Variable(fun () -> [])
                            Rotation        = game_state.Player.DirectionFire.Value
                            isActive        = variable(true)
                            Event           = None
                            BBox            = 
                                new BoundingBox(new Vector3(game_state.Player.Position.Value.X, game_state.Player.Position.Value.Y, 0.0f), new Vector3(game_state.Player.Position.Value.X + 15.0f, game_state.Player.Position.Value.Y + 15.0f, 0.0f)) 
                            Type            = 1
                        }:: !game_state.Player.Projectiles

                    game_state.Player.Event <- Some({i = 0; s = "Shot"})
                    do! yield_
                    game_state.Player.Event <- None

                    return ()
               
              }


        let rec enemySpawn() =

            co{
                // calculation for enemies spawning
                let dirTB = random.Next(1, (screenWidth) |> int) |> float32
                let dirLR = random.Next(1, (screenHeight) |> int) |> float32

                let choice : int ref = ref 0
                
                // different level, different spawning
                if game_state.Level = "Square" then
                    choice.Value <- random.Next(1,5)   
                elif game_state.Level = "Triangle" then
                    choice.Value <- random.Next(3,5)
                elif game_state.Level = "Line" then
                    choice.Value <- random.Next(1,3)
                elif game_state.Level = "Point" then
                    choice.Value <- random.Next(3,5)
               
                
                let x : float32 ref = ref 1.0f
                let y : float32 ref = ref 1.0f
                let a : float32 ref = ref 0.0f
                let direction : Vector2 ref = ref !game_state.Player.Position

                // spawning on the top side
                if choice.Value = 1 then 
                    x.Value <- dirTB
                    y.Value <- 110.0f
  
                // spawning on the bottom side
                elif choice.Value = 2 then
                    x.Value <- dirTB 
                    y.Value <- 768.0f + 50.0f
  
                // spawning on the left side
                elif choice.Value = 3 then
                    x.Value <- 0.0f
                    y.Value <- dirLR
  
                // spawning on the right side
                elif choice.Value = 4 then
                    x.Value <- 1366.0f + 50.0f
                    y.Value <- dirLR
                
                direction.Value <- Vector2(game_state.Player.Position.Value.X + 30.0f - x.Value, game_state.Player.Position.Value.Y + 30.0f - y.Value)
                a.Value <- Math.Atan2(direction.Value.X |> float, direction.Value.Y |> float) |> float32
                
                // no spawning
                if game_state.Level = "Tutorial" then
                    do! wait 1.0
//                    let newEnemy =
//                        {                
//                            Position = variable(Vector2(x.Value,y.Value))                
//                            Direction = Vector2(direction.Value.X,direction.Value.Y)
//                            IsActive = variable(true) 
//                            Type = 0
//                            Heading = -a.Value + MathHelper.PiOver2                               
//                            BBox =
//                                new BoundingBox(new Vector3(x.Value - 22.5f, y.Value - 22.5f, 0.0f), new Vector3(x.Value + 45.0f, y.Value + 45.0f, 0.0f))
//                            Event = None
//                            Velocity = 30.0f
//                        } 
// 
                        
                    do! enemySpawn ()

                // TODO: balance wait value and speed value for each enemy
                if game_state.Level = "Square" then
                    do! wait ((random.Next(1, 2) |> float) / 5.0)
                    let newEnemy =
                        {                
                            Position = variable(Vector2(x.Value,y.Value))                
                            IsActive = variable(true) 
                            Type = 0
                            Heading = -a.Value + MathHelper.PiOver2                               
                            BBox =
                                new BoundingBox(new Vector3(x.Value - 22.5f, y.Value - 22.5f, 0.0f), new Vector3(x.Value + 22.5f, y.Value + 22.5f, 0.0f))
                            Velocity = variable(Vector2.Zero)
                            Speed = 200.0f
                            Mass = 10.0f
                        } 
 
                    
                    game_state.Enemies := newEnemy :: !game_state.Enemies
                        
                    do! enemySpawn ()

                elif game_state.Level = "Triangle" then
                    do! wait ((random.Next(1, 2) |> float) / 10.0)
                    let newEnemy =
                        {                
                            Position = variable(Vector2(x.Value,y.Value))                
                            IsActive = variable(true) 
                            Type = 1
                            Heading = -a.Value + MathHelper.Pi
                            BBox =
                                new BoundingBox(new Vector3(x.Value - 15.0f, y.Value - 13.0f, 0.0f), new Vector3(x.Value + 30.0f, y.Value + 36.0f, 0.0f))
                            Speed = 300.0f
                            Velocity = variable(Vector2.Zero)
                            Mass = 6.0f
                        } 
 
                    game_state.Enemies := newEnemy :: !game_state.Enemies 
                    do! enemySpawn ()

                elif game_state.Level = "Line" then
                    do! wait ((random.Next(1, 2) |> float) / 12.0)
                    let newEnemy =
                        {                
                            Position = variable(Vector2(x.Value,y.Value))                
                            IsActive = variable(true) 
                            Type = 2
                            Heading = -a.Value
                            BBox =
                                new BoundingBox(new Vector3(x.Value - 2.5f, y.Value - 34.0f, 0.0f), new Vector3(x.Value + 5.0f, y.Value + 68.0f, 0.0f))
                            Speed = 400.0f
                            Velocity = variable(Vector2.Zero)
                            Mass = 4.0f
                        } 
 
                    game_state.Enemies := newEnemy :: !game_state.Enemies
                    do! enemySpawn ()

                elif game_state.Level = "Point" then
                    do! wait ((random.Next(1, 2) |> float) / 15.0)
                    let newEnemy =
                        {                
                            Position = variable(Vector2(x.Value,y.Value))                
                            IsActive = variable(true) 
                            Type = 3
                            Heading = a.Value
                            BBox =
                                new BoundingBox(new Vector3(x.Value - 5.0f, y.Value - 5.0f, 0.0f), new Vector3(x.Value + 10.0f, y.Value + 10.0f, 0.0f))
                            Speed = 500.0f
                            Velocity = variable(Vector2.Zero)
                            Mass = 2.0f
                        } 
 
                    game_state.Enemies := newEnemy :: !game_state.Enemies 
                    do! enemySpawn ()
             }

             in (repeat_(shootProjectiles()) .||> repeat_(playerMove()) .||> enemySpawn() ) .||> Timer()  |> ref

    let update_script() = main.Value <- update_ai main.Value